#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <spawn.h>
#include <errno.h>
#include <unsupported_api.h>
#include "pthread_impl.h"

#define cmd_max_length 128
#define cmd_max_num 20

extern char **__environ;

enum {
	s_normal,
	s_rod,
	s_quotes
};

void split(char* src, char** dest, int* num)
{
	int i = 0, status = s_normal;

	if (!src || *src == '\0') {
		return;
	}

	while (*src) {
		if (*src == ' ') {
			if (status == s_normal) {
				src += strspn(src, " ") - 1;
				(*num)++;
				i = 0;
			} else if (status == s_rod) {
				status = s_normal;
				dest[*num][i++] = *src;
			} else if (status == s_quotes) {
				dest[*num][i++] = *src;
			}
		} else if (*src == '"') {
			status = status == s_normal ? s_quotes : s_normal;
			dest[*num][i++] = *src;
		} else if (*src == '\\') {
			status = s_rod;
			dest[*num][i++] = *src;
		} else {
			dest[*num][i++] = *src;
		}
		src++;
	}
	(*num)++;
}

int system(const char *cmd)
{
	pid_t pid;
	sigset_t old, reset;
	struct sigaction sa = { .sa_handler = SIG_IGN }, oldint, oldquit;
	int status = -1, ret;
	posix_spawnattr_t attr;
	int cmdnum = 0;
	char** argv;
	const int extrapara = 2;
	char cmddata[cmd_max_length];
	char* cmdspilt[cmd_max_num];

	pthread_testcancel();

	if (!cmd) return 1;

	sigaction(SIGINT, &sa, &oldint);
	sigaction(SIGQUIT, &sa, &oldquit);
	sigaddset(&sa.sa_mask, SIGCHLD);
	sigprocmask(SIG_BLOCK, &sa.sa_mask, &old);

	sigemptyset(&reset);
	if (oldint.sa_handler != SIG_IGN) sigaddset(&reset, SIGINT);
	if (oldquit.sa_handler != SIG_IGN) sigaddset(&reset, SIGQUIT);
	posix_spawnattr_init(&attr);
	posix_spawnattr_setsigmask(&attr, &old);
	posix_spawnattr_setsigdefault(&attr, &reset);
	posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK);

	for (int i = 0; i < cmd_max_num; i++) {
		cmdspilt[i] = (char*)calloc(cmd_max_length, sizeof(char));
		memset(cmdspilt[i], '\0', cmd_max_length);
	}
	cmd += strspn(cmd, " ");
	strcpy(cmddata, cmd);
	split(cmddata, cmdspilt, &cmdnum);
	argv = (char**)calloc(cmdnum + extrapara, sizeof(char*));
	argv[0] = "shell";
	for (int i = 0; i < cmdnum; i++) {
		argv[i+1] = cmdspilt[i];
	}

	ret = posix_spawn(&pid, "/bin/shell", 0, &attr, argv, __environ);
	posix_spawnattr_destroy(&attr);

	if (!ret) while (waitpid(pid, &status, 0)<0 && errno == EINTR);
	sigaction(SIGINT, &oldint, NULL);
	sigaction(SIGQUIT, &oldquit, NULL);
	sigprocmask(SIG_SETMASK, &old, NULL);

	free(argv);
	for (int i = 0; i < cmd_max_num; i++) {
		free(cmdspilt[i]);
	}
	if (ret) errno = ret;
	return status;
}
